﻿using Dapper;
using DapperExtensions;
using XB.Core.DAO.Audit;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace XB.Core.DAO
{
    public abstract class SqlClient
    {
        protected string _connectionString;

        public SqlClient(string connectionString, int timeOutInSecond = 10)
        {
            _connectionString = connectionString;
            TimeOutInSecond = timeOutInSecond;
        }

        public int TimeOutInSecond { get; private set; }

        public IEnumerable<T> Query<T>(string sql, dynamic parameters = null, int timeOutInSecond = 0)
        {
            using (var conn = GetConnection())
            {
                conn.Open();
                return conn.Query<T>(sql, new Dapper.DynamicParameters(parameters), null, true, timeOutInSecond > 0 ? timeOutInSecond : TimeOutInSecond);
            }
        }

        public int Count(string sql, object param)
        {
            return Query<int>(sql, param).FirstOrDefault();
        }

        public int Execute(string sql, object param = null)
        {
            using (var conn = GetConnection())
            {
                conn.Open();
                return conn.Execute(sql, param, null, TimeOutInSecond);
            }
        }

        public void Run(Action<DbConnection, int> action)
        {
            using (var conn = GetConnection())
            {
                conn.Open();
                action(conn, TimeOutInSecond);
            }
        }

        public T Run<T>(Func<DbConnection, int, T> func)
        {
            using (var conn = GetConnection())
            {
                conn.Open();
                return func(conn, TimeOutInSecond);
            }
        }

        public void Trans(Action<DbTransaction, int> transAction)
        {
            Run((conn, timeOut) =>
            {
                using (var trans = conn.BeginTransaction())
                {
                    try
                    {
                        transAction(trans, timeOut);
                        trans.Commit();
                    }
                    catch (Exception)
                    {
                        trans.Rollback();
                        throw;
                    }
                }
            });
        }

        public IEnumerable<DbDataReader> Query(string sql)
        {
            using (var conn = GetConnection())
            {
                conn.Open();

                var cmd = GetCommand(sql, conn);
                cmd.CommandTimeout = TimeOutInSecond;

                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        yield return reader;
                    }
                }
            }
        }

        public IEnumerable<DbDataReader> QueryForModify(string sql)
        {
            foreach (var model in Query(sql))
            {
                yield return AuditManager.CreateProxy(model);
            }
        }

        public IEnumerable<T> Query<T>(string sql, Func<DbDataReader, T> parser)
        {
            foreach (var reader in Query(sql))
            {
                yield return parser(reader);
            }
        }

        public T Get<T>(int id, IDbTransaction transaction = null) where T : class
        {
            return Run((connection, timeOut) =>
            {
                return connection.Get<T>(id, transaction, timeOut);
            });
        }

        public T GetForModify<T>(int id, IDbTransaction transaction = null) where T : class
        {
            return AuditManager.CreateProxy(Get<T>(id, transaction));
        }

        /// <summary>
        /// 获取多个实体
        /// </summary>
        /// <param name="predicate">查询条件</param>
        /// <param name="sort">排序条件</param>
        /// <param name="transaction"></param>
        /// <param name="buffered"></param>
        /// <returns></returns>
        public IEnumerable<T> Get<T>(object predicate = null, IList<ISort> sort = null, IDbTransaction transaction = null, bool buffered = false)
            where T : class
        {
            return Run((connection, timeOut) =>
            {
                return connection.GetList<T>(predicate, sort, transaction, timeOut, buffered);
            });
        }

        public IEnumerable<T> GetForModify<T>(object predicate = null, IList<ISort> sort = null, IDbTransaction transaction = null, bool buffered = false)
            where T : class
        {
            foreach (var model in Get<T>(predicate, sort, transaction, buffered))
            {
                yield return AuditManager.CreateProxy(model);
            }
        }

        /// <summary>
        /// 查实体条目数
        /// </summary>
        /// <example>
        /// var predicate = Predicates.Field<Person>(f => f.DateCreated, Operator.Lt, DateTime.UtcNow.AddDays(-5));
        /// int count = cn.Count<Person>(predicate);
        /// </example>
        /// <param name="predicate">查询条件</param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public int Count<T>(object predicate = null, IDbTransaction transaction = null) where T : class
        {
            return Run((connection, timeOut) =>
            {
                return connection.Count<T>(predicate, transaction, timeOut);
            });
        }

        /// <summary>
        /// 分页
        /// </summary>
        /// <param name="predicate">查询条件</param>
        /// <param name="sort">排序条件</param>
        /// <param name="pageIndex">页码</param>
        /// <param name="pageSize">每页条数</param>
        /// <param name="transaction">事物</param>
        /// <param name="buffered">？</param>
        /// <returns></returns>
        public IEnumerable<T> GetPage<T>(object predicate = null, IList<ISort> sort = null, int pageIndex = 0, int pageSize = 0,
            IDbTransaction transaction = null, bool buffered = false) where T : class
        {
            sort = sort ?? new List<ISort>() { new Sort { PropertyName = "ID", Ascending = true } };
            return Run((connection, timeOut) =>
            {
                return connection.GetPage<T>(predicate, sort, pageIndex, pageSize, transaction, timeOut, buffered);
            });
        }

        /// <summary>
        /// 获取一批数据
        /// </summary>
        /// <param name="predicate">查询条件</param>
        /// <param name="sort">排序条件</param>
        /// <param name="firstResult">开始位置</param>
        /// <param name="maxResults">最多获取条目数</param>
        /// <param name="transaction"></param>
        /// <param name="buffered"></param>
        /// <returns></returns>
        public IEnumerable<T> GetSet<T>(object predicate = null, IList<ISort> sort = null, int firstResult = 0, int maxResults = 0,
            IDbTransaction transaction = null, bool buffered = false) where T : class
        {
            sort = sort ?? new List<ISort>() { new Sort { PropertyName = "ID", Ascending = true } };
            return Run((connection, timeOut) =>
            {
                return connection.GetPage<T>(predicate, sort, firstResult, maxResults, transaction, TimeOutInSecond, buffered);
            });
        }

        public IEnumerable<T> GetSetForModify<T>(object predicate = null, IList<ISort> sort = null, int firstResult = 0, int maxResults = 0,
            IDbTransaction transaction = null, bool buffered = false) where T : class
        {
            foreach (var model in GetSet<T>(predicate, sort, firstResult, maxResults, transaction, buffered))
            {
                yield return AuditManager.CreateProxy(model);
            }
        }

        /// </example>
        /// <param name="entity"></param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public dynamic Insert<T>(T entity, IDbTransaction transaction = null) where T : class, IEntityBase
        {
            var e = entity as IEntityBase;
            if (e != null)
            {
                e.CreateTime = DateTime.UtcNow;
                e.RowVersion = null;
            }
            return Run((connection, timeOut) =>
            {
                return connection.Insert<T>(entity, transaction, timeOut);
            });
        }

        public void Insert<T>(IEnumerable<T> entities, IDbTransaction transaction = null) where T : class, IEntityBase
        {
            foreach (var entity in entities)
            {
                var e = entity as IEntityBase;
                if (e != null)
                {
                    e.CreateTime = DateTime.UtcNow;
                    e.RowVersion = null;
                }
            }

            Run((connection, timeOut) =>
            {
                connection.Insert<T>(entities, transaction, timeOut);
            });
        }

        public T SaveOrUpdate<T>(T entity, IDbTransaction transaction = null) where T : class, IEntityBase
        {
            if (entity.ID > 0)
            {
                Update(entity, transaction);
            }
            else
            {
                entity.ID = Insert(entity, transaction);
            }
            return entity;
        }

        public bool Update<T>(T entity, IDbTransaction transaction = null) where T : class
        {
            var e = entity as IEntityBase;
            if (e != null)
            {
                e.CreateTime = null;
                e.RowVersion = null;
            }

            return Run((connection, timeOut) =>
            {
                var res = connection.Update(entity, transaction, timeOut);
                if (res && entity is IAudit)
                {
                    AuditManager.AppendAudit(connection, entity, EnumAuditAction.Update, null, transaction);
                }

                return res;
            });
        }

        public bool Delete<T>(T entity, IDbTransaction transaction = null) where T : class, IEntityBase
        {
            return Run((connection, timeOut) =>
            {
                var res = connection.Delete(entity, transaction, timeOut);
                if (res && entity is IAudit)
                {
                    AuditManager.AppendAudit(connection, entity, EnumAuditAction.Update, null, transaction);
                }

                return res;
            });
        }

        /// <summary>
        /// 按条件删除
        /// </summary>
        /// <example>
        /// predicate是DapperExtension提供的查询条件接口
        /// var predicate = Predicates.Field<Person>(f => f.DateCreated, Operator.Lt, DateTime.UtcNow.AddDays(-5));
        /// int count = cn.Delete<Person>(predicate);
        /// </example>
        /// <param name="predicate">条件</param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public bool Delete<T>(object predicate, IDbTransaction transaction = null) where T : class, IEntityBase
        {
            return Run((connection, timeOut) =>
            {
                return connection.Delete<T>(predicate, transaction, timeOut);
            });
        }

        protected abstract DbConnection GetConnection();

        protected abstract DbCommand GetCommand(string sql, DbConnection conn);
    }
}
